/*
 * manejoDeNgramas.cpp
 *
 *  Created on: 29/11/2014
 *      Author: nico
 */

#include <iostream>
#include <string>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <fstream>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/map.hpp>
#include <boost/lexical_cast.hpp>
#include "Unigrama.hpp"
#include "Bigrama.hpp"
#include "Trigrama.hpp"

using namespace std;
using namespace boost;

void mostrarUnigramas (map <string,Unigrama*> &mapa){

	map <string,Unigrama*>::iterator iterador;

    cout << "UNIGRAMAS" << endl;
    for (iterador = mapa.begin(); iterador != mapa.end(); iterador++){
    	cout << iterador->second->getTermino() << ":" << iterador->second->getOcurrencias() << ":" << iterador->second->getProbabilidad() << endl;
    }
}

void mostrarBigramas (map <string,Bigrama*> &mapa){

	map <string,Unigrama*>:: iterator iteradorUnigramas;
	map <string,Bigrama*>::iterator iteradorBigramas;

	cout << "BIGRAMAS" << endl;
		for (iteradorBigramas = mapa.begin(); iteradorBigramas != mapa.end(); iteradorBigramas++){
			for (iteradorUnigramas = (*iteradorBigramas).second->getIteradorTerminos(); iteradorUnigramas != (*iteradorBigramas).second->ultimoTermino()  ; iteradorUnigramas++  ){
				cout << iteradorBigramas->second->getClave() << " " << iteradorUnigramas->second->getTermino() << ":" << iteradorUnigramas->second->getOcurrencias() << endl;
	        }
		}
}

void mostrarTrigramas (map <string,Trigrama*> &mapa){

	map <string,Unigrama*>::iterator iteradorUnigramas;
	map <string,Trigrama*>::iterator iteradorTrigramas;

	   cout << "TRIGRAMAS"<< endl;
	   for (iteradorTrigramas = mapa.begin(); iteradorTrigramas != mapa.end(); iteradorTrigramas++){
		   for (iteradorUnigramas = (*iteradorTrigramas).second->getIteradorTerminos(); iteradorUnigramas != (*iteradorTrigramas).second->ultimoTermino()  ; iteradorUnigramas++  ){
			   cout << iteradorTrigramas->second->getClave() << " " << iteradorUnigramas->second->getTermino() << ":" << iteradorUnigramas->second->getOcurrencias() << endl;
		    }
	   }
}

void agregarUnigrama (map <string,Unigrama*> &mapa, string termino, size_t* bytesUsados){

	Unigrama* pUnigrama;

	if (mapa.find(termino) == mapa.end()){
	//si no esta el termino creo un nuevo unigrama con el termino y lo agrego al mapa

		pUnigrama = new Unigrama (termino);
		mapa.insert(pair <string,Unigrama*> (termino,pUnigrama));
		// overhead nodo + tam nodo + 2*string en heap + overhead clave + tam par
		*bytesUsados = *bytesUsados + 32 + 16 + (termino.capacity()+1) * 2 + 30 + 16;
	}else{
		//si esta hay que aumentar la cantidad de ocurrencias
		mapa[termino]->inc();
	}
}

void agregarBigrama (map <string,Bigrama*> &mapa, string clave, string termino, size_t* bytesUsados){

	Bigrama* pBigrama;

	if (mapa.find(clave) == mapa.end()){
		//si no esta la clave creo un nuevo bigrama con la clave y agrego el termino
		pBigrama = new Bigrama (clave);
		pBigrama->agregarTermino(termino);
		mapa.insert(pair <string,Bigrama*> (clave,pBigrama));
		*bytesUsados = *bytesUsados+ 16 + 72 + (clave.capacity()+1)*2 + 120 + (termino.capacity()+1)*2 + 32*2;

	}else{
		//si esta queda ver si tengo que agregar un termino nuevo o aumentar las ocurrencias de uno existente
		if (mapa[clave]->existeTermino(termino)){
			mapa[clave]->incOcurrencias(&termino);
		}else {
			mapa[clave]->agregarTermino(termino);
			*bytesUsados = *bytesUsados + 8 +32 + 32 + (termino.capacity()+1)*2 + 8 + 60;
		}
	}
}

void agregarTrigrama (map <string,Trigrama*> &mapa, string clave,string termino, size_t* bytesUsados){

	Trigrama* pTrigrama;

		if (mapa.find(clave) == mapa.end()){
			//si no esta la clave creo un nuevo Trigrama con la clave y agrego el termino
			pTrigrama = new Trigrama (clave);
			pTrigrama->agregarTermino(termino);
			mapa.insert(pair <string,Trigrama*> (clave,pTrigrama));
			*bytesUsados = *bytesUsados+ 8 *2 + 72 + (clave.capacity()+1)*2 + 120 + (termino.capacity()+1)*2 + 32*2;

		}else{
			//si esta queda ver si tengo que agregar un termino nuevo o aumentar las ocurrencias de uno existente
			if (mapa[clave]->existeTermino(termino)){
				mapa[clave]->incOcurrencias(&termino);
			}else {
				mapa[clave]->agregarTermino(termino);
				*bytesUsados = *bytesUsados + 8 +32 + 32 + (termino.capacity()+1)*2 + 8 + 60;
			}
		}
}

void guardarNgramas(map <string,Unigrama*> &mapaUnigramas, map <string,Bigrama*> &mapaBigramas,
			map <string,Trigrama*> &mapaTrigramas, vector <string> &terminos, size_t* bytesUsados){

	unsigned int posVec;

	for ( posVec = 0; posVec <terminos.size(); posVec++){
		agregarUnigrama (mapaUnigramas,terminos[posVec],bytesUsados);
	}

	posVec=0;
	while (posVec < terminos.size()){
		string clave = terminos.at(posVec);
		posVec++;
		if (posVec < terminos.size()){
			agregarBigrama(mapaBigramas,clave,terminos.at(posVec),bytesUsados);
		}
	}

	posVec=0;
	while (posVec < terminos.size()){
		string clave = terminos.at(posVec);
		posVec++;
		if (posVec < terminos.size()){
			clave = clave + " " + terminos.at(posVec);
		}
		if ((posVec+1) < terminos.size()){
			agregarTrigrama(mapaTrigramas,clave,terminos.at(posVec+1),bytesUsados);
		}
	}
}

void borrarUnigramas (map <string,Unigrama*> &mapa){
	map <string,Unigrama*>:: iterator iterador;
	for (iterador = mapa.begin(); iterador != mapa.end(); iterador++){
		delete (*iterador).second;
	}
	mapa.clear();
}

void borrarBigramas (map <string,Bigrama*> &mapa){
	map <string,Bigrama*>:: iterator iterador;
	for (iterador = mapa.begin(); iterador != mapa.end(); iterador++){
		delete (*iterador).second;
	}
	mapa.clear();
}

void borrarTrigramas (map <string,Trigrama*> &mapa){
	map <string,Trigrama*>:: iterator iterador;
	for (iterador = mapa.begin(); iterador != mapa.end(); iterador++){
		delete (*iterador).second;
	}
	mapa.clear();
}


void serializarNgramas (map <string,Unigrama*> &mapaUnigramas, map <string,Bigrama*> &mapaBigramas,
			map <string,Trigrama*> &mapaTrigramas,int version){

	string nomArchUnigramas = "particiones/unigramas_";
	string nomArchBigramas = "particiones/bigramas_";
	string nomArchTrigramas = "particiones/trigramas_";
	string extension = ".txt";

	//Agrego la extension al nombre de archivo
	nomArchUnigramas = nomArchUnigramas + lexical_cast<std::string>(version) + extension;
	nomArchBigramas = nomArchBigramas + lexical_cast<std::string>(version) + extension;
	nomArchTrigramas = nomArchTrigramas + lexical_cast<std::string>(version) + extension;


	ofstream archUnigramas(nomArchUnigramas.c_str());
	ofstream archBigramas(nomArchBigramas.c_str());
	ofstream archTrigramas(nomArchTrigramas.c_str());

	boost::archive::text_oarchive salidaUnigramas(archUnigramas);
	boost::archive::text_oarchive salidaBigramas(archBigramas);
	boost::archive::text_oarchive salidaTrigramas(archTrigramas);

	cout << "persistiendo modelo..." << endl;
	salidaUnigramas << mapaUnigramas;
	salidaBigramas << mapaBigramas;
	salidaTrigramas << mapaTrigramas;
}

void hidratarUnigramas (map <string,Unigrama*> &mapaUnigramas, int version){

	string nomArchUnigramas = "particiones/unigramas_";
	string extension = ".txt";

	//Agrego la extension al nombre de archivo
	nomArchUnigramas = nomArchUnigramas + lexical_cast<std::string>(version) + extension;
	ifstream archUnigramas(nomArchUnigramas.c_str());
	boost::archive::text_iarchive entradaUnigramas(archUnigramas);
	entradaUnigramas >> mapaUnigramas;
}

void hidratarBigramas (map <string,Bigrama*> &mapaBigramas, int version){

	string nomArchBigramas = "particiones/bigramas_";
	string extension = ".txt";

	//Agrego la extension al nombre de archivo
	nomArchBigramas = nomArchBigramas + lexical_cast<std::string>(version) + extension;
	ifstream archBigramas(nomArchBigramas.c_str());
	boost::archive::text_iarchive entradaBigramas(archBigramas);
	entradaBigramas >> mapaBigramas;
}

void hidratarTrigramas (map <string,Trigrama*> &mapaTrigramas, int version){

	string nomArchTrigramas = "particiones/trigramas_";
	string extension = ".txt";

	//Agrego la extension al nombre de archivo
	nomArchTrigramas = nomArchTrigramas + lexical_cast<std::string>(version) + extension;
	ifstream archTrigramas(nomArchTrigramas.c_str());
	boost::archive::text_iarchive entradaTrigramas(archTrigramas);
	entradaTrigramas >> mapaTrigramas;
}

void mergearUnigramas (map <string,Unigrama*> &mapaResultado, int ultVersion){

	map <string,Unigrama*> mapUni;
	map <string,Unigrama*>::iterator it;
	int version = 1;
	hidratarUnigramas (mapaResultado,version);

	for (version = 2; version<= ultVersion; version++){
		hidratarUnigramas(mapUni,version);
		//por cada elemento verifico si esta en el mapa resultado
		for (it = mapUni.begin(); it!= mapUni.end(); it++){
			if ( (mapaResultado.find(it->first) == mapaResultado.end()) ){
				//si no esta lo agrego
				Unigrama* p = new Unigrama(*(it->second));  // uso el constructor de copia
				mapaResultado.insert(pair <string,Unigrama*> (it->first,p));
			} else {
				//si esta, solo sumo las ocurrencias
				mapaResultado[it->second->getTermino()]->sumarOcurrencias(it->second->getOcurrencias());
			}
		}
		borrarUnigramas(mapUni);
		cout << " version " << version << " termino" << endl;
	}
}

void mergearBigramas (map <string,Bigrama*> &mapaResultado, int ultVersion){

	Bigrama* p;
	map <string,Bigrama*> mapBig;
	map <string,Bigrama*>::iterator it;
	map <string,Unigrama*>::iterator itTerm;
	int version = 1;

	hidratarBigramas (mapaResultado,version);

	for (version = 2; version<= ultVersion; version++){
		hidratarBigramas(mapBig,version);
		for (it = mapBig.begin(); it != mapBig.end(); it++){
			if (mapaResultado.find(it->first) == mapaResultado.end()){
				//no esta la clave tengo que crear una clave  nueva con todos los terminos
				p = new Bigrama (it->first);
				mapaResultado.insert(pair <string,Bigrama*> (p->getClave(),p));
				for (itTerm = it->second->getIteradorTerminos(); itTerm != it->second->ultimoTermino() ; itTerm++){
					//agrego cada uno de los terminos con sus respectivos valores
					mapaResultado [it->first]->agregarTermino(itTerm->second);
				}
			} else {
				// si esta la clave tengo que verificar cada uno de los terminos
				for (itTerm = it->second->getIteradorTerminos(); itTerm != it->second->ultimoTermino() ; itTerm++){
					//por cada termino verifico si esta en mapa resultado, si esta sumo ocurrencias, no esta lo agrego
					if (mapaResultado[it->first]->existeTermino(itTerm->first)){
						//tengo que sumar ocurrencias
						mapaResultado[it->first]->sumarOcurrencias(itTerm->first, itTerm->second->getOcurrencias());
					}else {
						//tengo que agregar el termino
						mapaResultado[it->first]->agregarTermino(itTerm->second);
					}
				}//FIN TERCER FOR
			}
		}//FIN SEGUNDO FOR
		cout << "termino version " << version << endl;
		borrarBigramas (mapBig);
	}//FIN PRIMER FOR
}

void mergearTrigramas (map <string,Trigrama*> &mapaResultado, int ultVersion){

	Trigrama* p;
	map <string,Trigrama*> mapTrig;
	map <string,Trigrama*>::iterator it;
	map <string,Unigrama*>::iterator itTerm;
	int version = 1;

	hidratarTrigramas (mapaResultado,version);

	for (version = 2; version<= ultVersion; version++){
		hidratarTrigramas(mapTrig,version);
		for (it = mapTrig.begin(); it != mapTrig.end(); it++){
			if (mapaResultado.find(it->first) == mapaResultado.end()){
				//no esta la clave tengo que crear una clave  nueva con todos los terminos
				p = new Trigrama (it->first);
				mapaResultado.insert(pair <string,Trigrama*> (p->getClave(),p));
				for (itTerm = it->second->getIteradorTerminos(); itTerm != it->second->ultimoTermino() ; itTerm++){
					//agrego cada uno de los terminos con sus respectivos valores
					mapaResultado [it->first]->agregarTermino(itTerm->second);
				}
			} else {
				// si esta la clave tengo que verificar cada uno de los terminos
				for (itTerm = it->second->getIteradorTerminos(); itTerm != it->second->ultimoTermino() ; itTerm++){
					//por cada termino verifico si esta en mapa resultado, si esta sumo ocurrencias, no esta lo agrego
					if (mapaResultado[it->first]->existeTermino(itTerm->first)){
						//tengo que sumar ocurrencias
						mapaResultado[it->first]->sumarOcurrencias(itTerm->first, itTerm->second->getOcurrencias());
					}else {
						//tengo que agregar el termino
						mapaResultado[it->first]->agregarTermino(itTerm->second);
					}
				}//FIN TERCER FOR
			}
		}//FIN SEGUNDO FOR
		cout << "termino version " << version << endl;
		borrarTrigramas (mapTrig);
	}//FIN PRIMER FOR
}
